/******************************************************************************
 *      Mestrado em Ciencia da Computacao - UFMS
 *		Redes de Computadores - Prof. Dr. Ronaldo Alves Ferreira
 *		Alunos: Geraldo Landre, Marcel Tolentino, Vinicius Bueno
 *****************************************************************************/
#include "queue_int.h"

void sync_remove_element(sync_queue_int *q, int element) {
    // verifica se a funcao freequeue foi chamada
    if(q == NULL || q->head == NULL || !q->available)
        return;
    
    cell_int *cp = q->head->link;
    cell_int *prev = q->head;

    if(prev->element == element)
    {
        q->head = cp;
        if(prev == q->tail)
            q->tail = cp;
        prev->link = NULL;
        free(prev);
    }
    else
    {
        for(;cp != NULL; prev = cp, cp = cp->link)
        {
            if(cp->element == element)
            {
                prev->link = cp->link;
                if(cp == q->tail)
                    q->tail = prev;
                free(cp);
                q->count--;
                break;
            }
        }
    }
    sem_post( &(q->sem_spaces) ); // sinal de espaco disponivel
}

// Operacao de insercao: coloca element na fila q.
void sync_enqueue( sync_queue_int *q, int element) {
    // verifica se a funcao freequeue foi chamada
    if(q == NULL || !q->available)
        return;

    sem_wait( &(q->sem_spaces) ); // espera por espaco

    cell_int *cp;
    cp = malloc( sizeof (cell_int));

    if(cp == NULL)
       exit(0);

    cp->element = element;
    cp->link = NULL;

    if (q->head == NULL)
       q->head = cp;
    else
       q->tail->link= cp;

    q->tail = cp;
    q->count++;
    sem_post( &(q->sem_items) ); // sinal de item disponivel
}

// Operacao de remocao: retira um objeto da fila q e
// devolve o valor desse objeto.
int sync_dequeue( sync_queue_int *q) {
    // verifica se a funcao freequeue foi chamada
    if(q == NULL || !q->available)
        return 0;

    sem_wait( &(q->sem_items) ); // espera por item na fila

    int result;
    cell_int *cp;
    cp = q->head;

    if (cp == NULL)
       return 0; // fila vazia

    result = cp->element;
    q->head = cp->link;
    free(cp);
    q->count--;
    sem_post( &(q->sem_spaces) ); // sinal de espaco disponivel

    return result;
}

// Cria uma fila vazia e devolve o endereco da nova fila.
sync_queue_int *sync_newqueue(u_short size) {
   sync_queue_int *q;
   q = malloc( sizeof (sync_queue_int));
   if (q == NULL)
       return NULL; // erro na alocacao

   q->head = NULL;
   q->max = size;
   q->count = 0;
   // semaforos:
   sem_init( &(q->sem_spaces), 0, size);
   sem_init( &(q->sem_items) , 0, 0);
   q->available = true;
   return q;
}


// Libera o espaco ocupado pela fila q. A funcao supoe que
// o espaco apontado pelo campo element de cada celula da
// lista tenha sido alocado por malloc.
void sync_freequeue( sync_queue_int *q) {
   if(q == NULL || !q->available)
        return;

   q->available = false; // previnir enqueues e dequeues durante o free
   
   cell_int *cp, *next;

   cp = q->head;
   while (cp != NULL) {
      next = cp->link;
      free(cp);
      cp = next;
   }
   sem_destroy(&(q->sem_items));
   sem_destroy(&(q->sem_spaces));
   free(q);
}

// Devolve 1 se a fila q esta vazia e 0 em caso contrario.

bool sync_queue_empty( sync_queue_int *q) {
    return q == NULL || q->count == 0;
}

// Devolve o comprimento da fila q.

int sync_queue_length( sync_queue_int *q) {
    if(q == NULL)
        return 0;
    return q->count;
}

int sync_queue_full(sync_queue_int *q) {
    return q->count == q->max;
}

// for testing.
void print_sync_queue_status(sync_queue_int *q) {
    if(q == NULL)
        printf("queue is null.\n");
    else if(sync_queue_empty(q))
        printf("queue is empty.\n");
    else {
        cell_int *cp;
        printf("[%d] - ", sync_queue_length(q));
        for (cp = q->head; cp != NULL; cp = cp->link)
            printf("%d;", cp->element);
        printf("\n");
    }
}

//
// Fila com exclusao mutua
//


void remove_element(queue_int *q, int element) {
    // verifica se a funcao freequeue foi chamada
    if(q == NULL || q->head == NULL)
        return;

    sem_wait(&(q->mutex));
    cell_int *cp = q->head->link;
    cell_int *prev = q->head;

    if(prev->element == element)
    {
        q->head = cp;
        if(prev == q->tail)
            q->tail = cp;
        prev->link = NULL;
        free(prev);
    }
    else
    {
        for(;cp != NULL; prev = cp, cp = cp->link)
        {
            if(cp->element == element)
            {
                prev->link = cp->link;
                if(cp == q->tail)
                    q->tail = prev;
                free(cp);
                q->count--;
                break;
            }
        }
    }
    sem_post(&(q->mutex));
}

// Operacao de insercao: coloca element na fila q.
void enqueue( queue_int *q, int element) {
    // verifica se a funcao freequeue foi chamada
    if(q == NULL)
        return;
    sem_wait(&(q->mutex));
    
    cell_int *cp;
    cp = malloc( sizeof (cell_int));

    if(cp == NULL)
       exit(0);

    cp->element = element;
    cp->link = NULL;

    if (q->head == NULL)
       q->head = cp;
    else
       q->tail->link= cp;

    q->tail = cp;
    q->count++;
    sem_post(&(q->mutex));
}

// Operacao de remocao: retira um objeto da fila q e
// devolve o valor desse objeto.
int dequeue( queue_int *q) {
    // verifica se a funcao freequeue foi chamada
    if(q == NULL)
        return 0;

    sem_wait(&(q->mutex));

    int result;
    cell_int *cp;
    cp = q->head;

    if (cp == NULL)
       return 0; // fila vazia

    result = cp->element;
    q->head = cp->link;
    free(cp);
    q->count--;
    sem_post(&(q->mutex));
    return result;
}

// Cria uma fila vazia e devolve o endereco da nova fila.
queue_int *newqueue(u_short size) {
   queue_int *q;
   q = malloc( sizeof (queue_int));
   if (q == NULL)
       return NULL; // erro na alocacao
   q->head = NULL;
   q->count = 0;
   q->max = size;
   sem_init(&(q->mutex), 0, 1);
   return q;
}

/*
 Libera o espaco ocupado pela fila q. A funcao supoe que
 o espaco apontado pelo campo element de cada celula da
 lista tenha sido alocado por malloc.
*/

void freequeue( queue_int *q) {
   if(q == NULL)
        return;

   cell_int *cp, *next;

   cp = q->head;
   while (cp != NULL) {
      next = cp->link;
      free(cp);
      cp = next;
   }
   sem_destroy(&(q->mutex));
   free(q);
}

// Devolve 1 se a fila q esta vazia e 0 em caso contrario.

bool queue_empty(queue_int *q) {
   return q == NULL || q->count == 0;
}

// Devolve o comprimento da fila q.

int queue_length(queue_int *q) {
    if(q == NULL)
        return 0;
    return q->count;
}

int queue_full(queue_int *q) {
    return q->count == q->max;
}

// for testing.
void print_queue_status(queue_int *q) {
    if(q == NULL)
        printf("queue is null.\n");
    else if(queue_empty(q))
        printf("queue is empty.\n");
    else
        printf("queue length: %d.\n", queue_length(q));
}
